package com.nbsaas.boot.executor;

import java.io.*;
import java.nio.file.*;
import java.util.concurrent.TimeUnit;

/**
 * Java沙箱执行器
 * 用于在安全的环境中执行JAR文件
 */
public class JavaSandboxExecutor {
    
    private final Path tempDir;
    private final long timeoutSeconds;
    private final int maxMemoryMB;
    
    /**
     * 创建沙箱执行器
     * @param timeoutSeconds 执行超时时间（秒）
     * @param maxMemoryMB 最大内存限制（MB）
     */
    public JavaSandboxExecutor(long timeoutSeconds, int maxMemoryMB) throws IOException {
        this.tempDir = Files.createTempDirectory("sandbox");
        this.timeoutSeconds = timeoutSeconds;
        this.maxMemoryMB = maxMemoryMB;
    }
    
    /**
     * 执行JAR文件
     * @param jarPath JAR文件路径
     * @return 执行结果
     */
    public ExecutionResult execute(String jarPath) {
        File jarFile = new File(jarPath);
        if (!jarFile.exists()) {
            return ExecutionResult.error("JAR文件不存在: " + jarPath);
        }

        try {
            // 1. 复制jar文件到临时目录
            Path targetJar = tempDir.resolve("app.jar");
            Files.copy(jarFile.toPath(), targetJar, StandardCopyOption.REPLACE_EXISTING);

            // 2. 创建安全策略文件
            createPolicyFile();

            // 3. 在沙箱环境中运行jar
            Process process = startProcess(targetJar);
            
            // 4. 捕获输出
            StringBuilder output = new StringBuilder();
            StringBuilder error = new StringBuilder();
            
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                 BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                
                // 启动输出读取线程
                Thread outputThread = new Thread(() -> {
                    try {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            output.append(line).append("\n");
                        }
                    } catch (IOException e) {
                        error.append("读取输出时发生错误: ").append(e.getMessage()).append("\n");
                    }
                });
                
                // 启动错误读取线程
                Thread errorThread = new Thread(() -> {
                    try {
                        String line;
                        while ((line = errorReader.readLine()) != null) {
                            error.append(line).append("\n");
                        }
                    } catch (IOException e) {
                        error.append("读取错误输出时发生错误: ").append(e.getMessage()).append("\n");
                    }
                });
                
                outputThread.start();
                errorThread.start();
                
                // 等待进程结束或超时
                boolean completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
                if (!completed) {
                    process.destroyForcibly();
                    return ExecutionResult.error("执行超时（" + timeoutSeconds + "秒）");
                }
                
                outputThread.join(1000);
                errorThread.join(1000);
                
                int exitCode = process.exitValue();
                return new ExecutionResult(exitCode, output.toString(), error.toString());
            }
            
        } catch (Exception e) {
            return ExecutionResult.error("执行过程中发生错误: " + e.getMessage());
        } finally {
            cleanup();
        }
    }
    
    private void createPolicyFile() throws IOException {
        String policyContent = 
            "grant {\n" +
            "    permission java.lang.RuntimePermission \"exitVM\";\n" +
            "    permission java.io.FilePermission \"${java.io.tmpdir}/sandbox-*\", \"read,write,delete\";\n" +
            "    permission java.util.PropertyPermission \"java.io.tmpdir\", \"read\";\n" +
            "    permission java.lang.RuntimePermission \"getenv.*\";\n" +
            "    permission java.util.PropertyPermission \"*\", \"read\";\n" +
            "    permission java.lang.RuntimePermission \"accessDeclaredMembers\";\n" +
            "    permission java.lang.reflect.ReflectPermission \"suppressAccessChecks\";\n" +
            "};";
        
        Path policyFile = tempDir.resolve("sandbox.policy");
        Files.write(policyFile, policyContent.getBytes());
    }
    
    private Process startProcess(Path targetJar) throws IOException {
        ProcessBuilder processBuilder = new ProcessBuilder(
            "java",
            "-Djava.security.manager",
            "-Djava.security.policy==" + tempDir.resolve("sandbox.policy").toString(),
            "-Xmx" + maxMemoryMB + "m",
            "-jar",
            targetJar.toString()
        )
        .directory(tempDir.toFile())
        .redirectErrorStream(true);
        
        processBuilder.environment().put("JAVA_HOME", System.getenv("JAVA_HOME"));
        return processBuilder.start();
    }
    
    private void cleanup() {
        try {
            Files.walk(tempDir)
                .sorted((a, b) -> b.compareTo(a))
                .forEach(path -> {
                    try {
                        Files.delete(path);
                    } catch (IOException e) {
                        System.err.println("清理文件失败: " + path);
                    }
                });
        } catch (IOException e) {
            System.err.println("清理临时目录失败");
        }
    }
    
    /**
     * 执行结果类
     */
    public static class ExecutionResult {
        private final int exitCode;
        private final String output;
        private final String error;
        private final boolean success;
        
        private ExecutionResult(int exitCode, String output, String error) {
            this.exitCode = exitCode;
            this.output = output;
            this.error = error;
            this.success = exitCode == 0 && error.isEmpty();
        }
        
        public static ExecutionResult error(String error) {
            return new ExecutionResult(-1, "", error);
        }
        
        public int getExitCode() {
            return exitCode;
        }
        
        public String getOutput() {
            return output;
        }
        
        public String getError() {
            return error;
        }
        
        public boolean isSuccess() {
            return success;
        }
    }
} 